5-2. 컨테이너 오케스트레이션 도구, 쿠버네티스 - 파트 2

이 노트의 실습 환경

  • 본문의 "실습" 블록은 모두 실제로 실행한 명령어와 실제 출력임 (2026-10-15 실행)
  • 환경: Windows 11 + Docker Desktop 내장 쿠버네티스 (멀티 노드 구성)
항목 값
쿠버네티스 버전 서버 v1.36.1 / kubectl v1.34.0
노드 desktop-control-plane 1대 + desktop-worker, desktop-worker2 2대
컨테이너 런타임 containerd 2.3.1
kube-proxy 모드 iptables
  • 파드 이름 끝의 랜덤 문자열(-6r4vr 등), IP 주소, AGE는 실행할 때마다 달라짐
  • Docker Desktop의 기본 설정(단일 노드)이라면 노드가 docker-desktop 1대로만 보이고, 나머지 결과는 거의 같음
  • 명령어는 Git Bash 기준. Git Bash는 /etc/...처럼 /로 시작하는 인자를 윈도 경로(C:/Program Files/Git/etc/...)로 바꿔 버리므로, 먼저 export MSYS_NO_PATHCONV=1을 실행해 두면 안전함 (파워셸·리눅스·맥OS 터미널은 필요 없음)

# 실습 전 확인: 지금 어느 클러스터를 조작하는지 확인 (CURRENT에 * 표시)
kubectl config get-contexts
# 출력:
# CURRENT   NAME             CLUSTER          AUTHINFO         NAMESPACE
# *         docker-desktop   docker-desktop   docker-desktop

# 클러스터를 구성하는 노드 확인
kubectl get nodes
# 출력:
# NAME                    STATUS   ROLES           AGE   VERSION
# desktop-control-plane   Ready    control-plane   14d   v1.36.1
# desktop-worker          Ready    <none>          14d   v1.36.1
# desktop-worker2         Ready    <none>          14d   v1.36.1
kubectl 버전 경고가 뜬다면

Warning: version difference between client (1.34) and server (1.36) exceeds the supported minor version skew of +/-1
→ kubectl과 클러스터의 버전 차이가 2단계 이상이라는 경고. 기본 명령은 대부분 동작하지만, kubectl을 클러스터 버전에 맞춰 업데이트하는 것이 좋음


5.7. 로드 밸런싱과 헬스 체크: 시스템의 가용성을 향상시키는 방법

5.7.1. 로드 밸런싱과 헬스 체크란

초보자를 위한 비유: 은행 창구

  • 로드 밸런싱 = 번호표 기계. 손님(요청)을 비어 있는 창구(컨테이너)로 골고루 보냄
  • 헬스 체크 = 창구 직원이 자리에 있는지 확인하는 매니저
  • 둘을 합치면: 자리를 비운 창구로는 번호표를 배정하지 않음 → 손님은 계속 업무를 볼 수 있음 (= 가용성 유지)

5.7.2. 가용성이란

가동률 계산식:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

             시스템이 정상적으로 가동된 시간
  가동률 = ─────────────────────────────────
                 시스템이 가동된 시간

  예) 30일(720시간) 중 7.2시간 장애 → (720 - 7.2) / 720 = 99%
가동률 별칭 1년 중 허용되는 장애 시간
99% 투 나인 약 3.65일
99.9% 쓰리 나인 약 8.76시간
99.99% 포 나인 약 52.6분
99.999% 파이브 나인 약 5.26분
단일 장애 지점 제거 = 이중화 + 자동 교체:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[이중화 전: 단일 장애 지점]          [이중화 후]

  요청                                요청
   │                                   │
   ▼                                   ▼
┌──────────┐                       [LB + 헬스 체크]
│ 컨테이너  │ ← 고장 = 전체 중단      ┌───┴────────┐
└──────────┘                       ▼            ▼
                                ┌────────┐  ┌────────┐
                                │컨테이너1│  │컨테이너2│
                                └────────┘  └────────┘
                                   ✗ 고장 →  요청을 2로만 전송
                                   (자동 발견)   (자동 교체)
용어 정리

  • 가용성 (Availability): 사용자가 원할 때 시스템이나 서비스를 사용할 수 있는 정도
  • 가동률: 정상 가동 시간 ÷ 전체 가동 시간. 가용성 평가 지표의 하나
  • 단일 장애 지점 (SPOF, Single Point of Failure): 장애가 나면 전체 시스템이 중단되는 구성 요소
  • 이중화 (Redundancy): 예비를 준비해 두고 고장 시 교체해 시스템 중단을 막는 방법

5.7.3. 쿠버네티스의 로드 밸런싱

왜 파드에 직접 접속하지 않고 서비스를 거칠까?

  • 파드는 언제든 죽고 새로 만들어지는 소모품이고, 새로 만들어질 때마다 IP 주소가 바뀜
  • 실제로 아래 실습에서 롤링 업데이트(5.10.4) 전후로 파드 IP가 모두 바뀌었음
  • 서비스는 변하지 않는 IP와 이름(my-service) 을 제공하고, 뒤의 파드 목록은 쿠버네티스가 자동으로 최신 상태로 유지함
  • 비유: 직원(파드)이 바뀌어도 대표 전화번호(서비스) 는 그대로

서비스의 패킷 전달 구조:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  [클라이언트]
       │ 서비스 IP(엔드포인트)로 패킷 전송
       ▼
 ┌──────────────────────────────────────┐
 │  노드의 netfilter                     │
 │  (iptables 또는 IPVS 규칙으로 NAT)     │ ← kube-proxy가 규칙을 갱신
 └──────────┬───────────────────────────┘
     ┌──────┼───────────┐
     ▼      ▼           ▼
  [파드 A] [파드 B]  [파드 C]

iptables와 IPVS 비교:

구분 iptables IPVS
정체 리눅스의 네트워크 관리 도구 리눅스 커널에 내장된 로드 밸런싱 기능
netfilter 사용 ○ (netfilter 설정을 변경) ○
규칙 보관 방식 규칙 목록을 위에서부터 차례로 검사 해시 테이블에 보존 (바로 찾아감)
동작 - 커널 모드에서 동작
속도 서비스가 많아질수록 느려짐 iptables보다 빠름
적합한 규모 일반 클러스터 서비스 10,000개 초과 대규모 클러스터
실습 1: 서비스로 로드 밸런싱 확인하기

5.9.6~5.9.7에서 만드는 my-deployment(nginx 파드 3개)와 my-service를 사용함. 각 파드가 응답에 자기 이름을 넣도록 바꾼 뒤, 클러스터 안의 테스트용 파드에서 서비스로 10번 요청을 보냄

# 1) 클러스터 안에서 요청을 보낼 테스트용 파드(client) 생성
kubectl run client --image=curlimages/curl --restart=Never --command -- sleep 3600

# 2) 각 nginx 파드의 첫 페이지를 "Hello from <파드 이름>"으로 변경
#    (Git Bash/리눅스 기준. $(...)는 명령 결과를 끼워 넣는 문법)
for p in $(kubectl get pods -l app=my-app -o name); do
  kubectl exec $p -- sh -c 'echo "Hello from $(hostname)" > /usr/share/nginx/html/index.html'
done

# 3) 서비스 이름으로 10번 요청
kubectl exec client -- sh -c 'for i in 1 2 3 4 5 6 7 8 9 10; do curl -s http://my-service:8080; done'
실제 출력:
Hello from my-deployment-5f9c57bbf7-k5slg
Hello from my-deployment-5f9c57bbf7-9w5sz
Hello from my-deployment-5f9c57bbf7-k5slg
Hello from my-deployment-5f9c57bbf7-c4wvz
Hello from my-deployment-5f9c57bbf7-c4wvz
Hello from my-deployment-5f9c57bbf7-c4wvz
Hello from my-deployment-5f9c57bbf7-c4wvz
Hello from my-deployment-5f9c57bbf7-9w5sz
Hello from my-deployment-5f9c57bbf7-9w5sz
Hello from my-deployment-5f9c57bbf7-c4wvz
kubectl exec client -- nslookup my-service.default.svc.cluster.local
# 출력:
# Server:		10.96.0.10          ← 클러스터 DNS 서버(CoreDNS)의 주소
# Name:	my-service.default.svc.cluster.local
# Address: 10.96.103.142          ← 서비스의 가상 IP(ClusterIP)
실습 2: kube-proxy가 만든 iptables 규칙 직접 보기

kube-proxy 파드 안에는 iptables 도구가 있으므로, 노드에 설정된 NAT 규칙 중 my-service 관련 줄만 뽑아볼 수 있음

# desktop-worker 노드에서 동작하는 kube-proxy 파드 찾기
kubectl get pods -n kube-system -l k8s-app=kube-proxy -o wide

# 그 kube-proxy 파드 안에서 NAT 테이블 규칙 출력 → my-service 관련 줄만 필터
kubectl exec -n kube-system kube-proxy-gpv8r -- sh -c 'iptables-save -t nat | grep "default/my-service"'
실제 출력 (핵심 줄만, 보기 쉽게 정렬):

① 서비스 IP:포트로 온 패킷을 my-service 전용 체인으로 보냄
-A KUBE-SERVICES -d 10.96.103.142/32 -p tcp --dport 8080 -j KUBE-SVC-I37Z43XJW6BD4TLV

② 전용 체인에서 확률로 파드 하나를 고름
-A KUBE-SVC-I37Z43XJW6BD4TLV ... -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-Y67ZTPCC2FTAXVBS
-A KUBE-SVC-I37Z43XJW6BD4TLV ... -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-RVD7P5CVIZTJGFR2
-A KUBE-SVC-I37Z43XJW6BD4TLV ... -j KUBE-SEP-RSNVYYY6S75AZ3QJ

③ 고른 파드의 실제 IP:80으로 목적지 주소를 바꿈 (DNAT)
-A KUBE-SEP-Y67ZTPCC2FTAXVBS -p tcp -j DNAT --to-destination 10.244.1.4:80
-A KUBE-SEP-RVD7P5CVIZTJGFR2 -p tcp -j DNAT --to-destination 10.244.2.6:80
-A KUBE-SEP-RSNVYYY6S75AZ3QJ -p tcp -j DNAT --to-destination 10.244.2.7:80
규칙 해석: 왜 확률이 1/3, 1/2, 나머지일까?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 패킷 도착 (목적지 10.96.103.142:8080)
    │
    ▼  규칙1: 1/3 확률로 당첨?
    ├─ 예 (1/3) ──────────────────────→ 파드 10.244.1.4
    │  아니오 (2/3)
    ▼  규칙2: 남은 것 중 1/2 확률로 당첨?
    ├─ 예 (2/3 × 1/2 = 1/3) ──────────→ 파드 10.244.2.6
    │  아니오 (1/3)
    ▼  규칙3: 무조건
    └─ (1/3) ─────────────────────────→ 파드 10.244.2.7

 → 위에서부터 차례로 검사하므로 확률을 1/3, 1/2, 1로 두면
   세 파드가 각각 정확히 1/3씩 선택됨 (= 무작위 균등 분배)
용어 정리

  • 라운드 로빈 (Round Robin): 여러 대상에 순서대로 하나씩 돌아가며 요청을 분배하는 방식
  • netfilter: 패킷 필터링과 네트워크 주소 변환을 수행하는 리눅스 커널의 기능
  • iptables: netfilter 설정을 변경하는 리눅스의 네트워크 관리 도구
  • IPVS (IP Virtual Server): 리눅스 커널에 내장된 로드 밸런싱 기능. 해시 테이블·커널 모드로 iptables보다 빠름
  • kube-proxy: 각 노드에서 동작하며 서비스의 패킷 전송 규칙을 갱신하는 에이전트
  • DNAT (Destination NAT): 패킷의 목적지 주소를 바꾸는 네트워크 주소 변환
  • CoreDNS: 서비스 이름을 IP 주소로 바꿔 주는 클러스터 내부 DNS 서버

2026-10 갱신

  • 실습에서 확인했듯 iptables 모드는 확률(무작위)로 파드를 선택함. 순서대로 돌리는 라운드 로빈은 IPVS 모드의 기본 스케줄러(rr)에 해당
  • kube-proxy에는 iptables·IPVS 외에 nftables 모드가 추가되어 v1.33에서 GA가 됨. 반면 IPVS 모드는 v1.35에서 지원 중단(deprecated)이 예고되어, 대규모 클러스터 대안으로는 nftables 모드가 권장되는 추세
  • Cilium 같은 eBPF 기반 CNI가 kube-proxy를 아예 대체하는 구성도 많이 쓰임

5.7.4. 쿠버네티스의 헬스 체크

방법 동작 판단 기준
명령어 실행 (exec) 파드의 컨테이너 안에서 지정된 명령어 실행 명령어 성공 여부 (종료 코드 0이면 성공)
TCP (tcpSocket) 지정 포트로 TCP 접속 시도 접속 성공 여부
HTTP (httpGet) 지정 경로로 HTTP 요청 응답 코드가 200~399면 성공
프로브 질문 실패하면
livenessProbe "살아 있니?" 컨테이너를 재시작
readinessProbe "손님 받을 준비 됐니?" 서비스의 전송 대상에서 제외 (재시작은 안 함)
startupProbe "다 켜졌니?" 기동이 끝날 때까지 다른 프로브를 보류
프로세스가 살아 있으면 정상 아닌가?

아님. 프로세스는 떠 있지만 내부가 꼬여서(교착 상태, 메모리 누수 등) 요청에 응답하지 못하는 경우가 흔함. 그래서 "프로세스가 있나?"가 아니라 "실제로 일을 하나?" 를 물어보는 것이 헬스 체크의 핵심

kubelet의 헬스 체크 흐름:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[kubelet] ──정기 확인──→ [파드의 컨테이너]
    │                      ├─ 명령어 실행
    │                      ├─ TCP 접속
    │                      └─ HTTP 요청
    ▼
 성공? ──예──→ 정상 → 그대로 유지
    │
    아니오
    ▼
 비정상 ─┬─→ (liveness 실패)  컨테이너 재시작
        └─→ (readiness 실패) 서비스의 요청 전송 대상에서 제외
실습 3: livenessProbe — 고장 나면 재시작

시작하면 /tmp/healthy 파일을 만들고, 30초 뒤 스스로 지워 버리는 컨테이너. kubelet은 5초마다 cat /tmp/healthy로 파일이 있는지 확인함

# liveness.yaml
apiVersion: v1
kind: Pod
metadata:
  name: liveness-demo
spec:
  containers:
  - name: app
    image: busybox:1.36
    # 파일 생성 → 30초 대기 → 파일 삭제(고장 흉내) → 계속 대기
    args: ["sh", "-c", "touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600"]
    livenessProbe:
      exec:
        command: ["cat", "/tmp/healthy"]  # 파일이 없으면 명령 실패 = 비정상
      initialDelaySeconds: 5              # 시작 후 5초 뒤부터 검사
      periodSeconds: 5                    # 5초마다 검사
kubectl apply -f liveness.yaml
kubectl describe pod liveness-demo     # 맨 아래 Events 확인
실제 출력 (약 50초 후, Events 부분):
  Type     Reason     Age               From     Message
  ----     ------     ----              ----     -------
  Normal   Started    50s               kubelet  Container started
  Warning  Unhealthy  9s (x3 over 19s)  kubelet  Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
  Normal   Killing    9s                kubelet  Container app failed liveness probe, will be restarted
kubectl get pod liveness-demo
# 실제 출력:
# NAME            READY   STATUS    RESTARTS     AGE
# liveness-demo   1/1     Running   1 (2s ago)   77s
실습 4: readinessProbe — 준비 안 되면 손님을 안 보냄

my-service가 고르는 레이블(app: my-app)을 붙인 nginx 파드를 하나 더 만들되, /ready.html 페이지가 있어야만 준비 완료로 판단하게 함. 처음에는 이 파일이 없음

# readiness.yaml
apiVersion: v1
kind: Pod
metadata:
  name: readiness-demo
  labels:
    app: my-app              # my-service의 셀렉터와 같은 레이블
spec:
  containers:
  - name: nginx
    image: nginx:stable
    readinessProbe:
      httpGet:
        path: /ready.html    # 이 페이지가 200을 돌려줘야 준비 완료
        port: 80
      periodSeconds: 3
kubectl apply -f readiness.yaml
kubectl get pod readiness-demo
# 실제 출력:
# NAME             READY   STATUS    RESTARTS   AGE
# readiness-demo   0/1     Running   0          15s     ← Running인데 READY는 0/1

# 서비스가 요청을 보낼 대상(엔드포인트)과 각각의 준비 상태 확인
kubectl get endpointslice -l kubernetes.io/service-name=my-service \
  -o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{"  ready="}{.conditions.ready}{"\n"}{end}'
# 실제 출력:
# 10.244.1.4  ready=true
# 10.244.2.6  ready=true
# 10.244.2.7  ready=true
# 10.244.1.7  ready=false    ← readiness-demo. 목록에는 있지만 요청은 안 보냄

kubectl describe pod readiness-demo | grep Unhealthy
# 실제 출력:
# Warning  Unhealthy  ...  Readiness probe failed: HTTP probe failed with statuscode: 404
# ready.html을 만들어 "준비 완료" 상태로 만듦
kubectl exec readiness-demo -- sh -c 'echo ok > /usr/share/nginx/html/ready.html'

kubectl get pod readiness-demo
# 실제 출력 (몇 초 후):
# NAME             READY   STATUS    RESTARTS   AGE
# readiness-demo   1/1     Running   0          25s

# 다시 엔드포인트 확인 → 10.244.1.7  ready=true 로 바뀜
용어 정리

  • kubelet: 각 노드에서 동작하며 파드를 실행·감시하는 에이전트 프로그램
  • 프로브 (Probe): kubelet이 컨테이너 상태를 진단하는 헬스 체크. exec·TCP·HTTP(·gRPC) 방식이 있음
  • livenessProbe: 실패하면 컨테이너를 재시작하는 프로브
  • readinessProbe: 실패하면 서비스 전송 대상에서 빼는 프로브
  • 엔드포인트슬라이스 (EndpointSlice): 서비스가 요청을 보낼 파드 IP 목록과 준비 상태를 담은 객체
  • RESTARTS: 컨테이너가 재시작된 횟수. 계속 늘어나면 문제가 있다는 신호

2026-10 갱신

  • 원서는 "재시작"과 "요청 전송 중지"를 한 문장에 묶었지만, 실습에서 본 것처럼 liveness / readiness가 따로 담당함
  • 판단 방법에 gRPC 방식이 추가됨 (v1.27 GA)


5.8. 스케일링: 시스템의 성능을 향상시키는 방법

5.8.1. 스케일링이란

상황 동작 효과
요청 집중 컨테이너 수 증가 처리 능력을 요청 규모에 맞춰 시스템을 정상 유지
야간 등 요청 감소 컨테이너 수 감소 리소스 소비량을 억제해 비용 절약

5.8.2. 스케일링하는 방법

구분 수평 스케일링 (Scale Out/In) 수직 스케일링 (Scale Up/Down)
방법 리소스의 수를 늘리거나 줄임 리소스의 성능을 높이거나 낮춤
예시 파드 3개 → 5개 파드 CPU 0.5코어 → 1코어
비유 계산대 직원을 더 뽑음 직원 한 명을 더 빠른 사람으로 바꿈
리소스와 처리 능력의 관계 선형에 가까움 (2배 → 약 2배) 선형에서 벗어나기 쉬움
한계 거의 없음 (노드만 있으면 계속 추가) 한 노드의 성능이 상한
수평 스케일링 vs 수직 스케일링:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[수평 스케일링: 개수를 늘림]       [수직 스케일링: 크기를 키움]

 [파드]  ──→  [파드] [파드] [파드]     [파드]  ──→  [  파  드  ]
                                                  (CPU·메모리 ↑)

  쿠버네티스: HPA                     쿠버네티스: VPA
  클러스터 자체: CA
용어 정리

  • 메트릭 (Metric): 스케일링 여부를 판단하는 데 사용하는 측정 데이터 (요청 수, CPU 사용률 등)
  • 임곗값 (Threshold): 동작을 일으키는 기준이 되는 값
  • 수평 스케일링: 리소스 수를 증감하는 방식 (스케일 아웃/인)
  • 수직 스케일링: 리소스 성능을 높이거나 낮추는 방식 (스케일 업/다운)

5.8.3. 쿠버네티스에서 파드의 수평 스케일링

실습 5: 수동 수평 스케일링
# 파드 수를 3개 → 5개로
kubectl scale deployment my-deployment --replicas=5
kubectl get deploy my-deployment
# 실제 출력:
# NAME            READY   UP-TO-DATE   AVAILABLE   AGE
# my-deployment   5/5     5            5           2m13s

# 다시 3개로
kubectl scale deployment my-deployment --replicas=3
# my-deployment   3/3     3            3           2m16s
HPA의 필요 파드 수 계산식:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

                                  현재 메트릭 값
 필요한 파드 수 = ⌈ 현재 파드 수 × ─────────────── ⌉   (⌈ ⌉ = 올림)
                                   목표 메트릭 값

 예) 목표 CPU 50%, 현재 평균 CPU 100%, 파드 2개
     → 2 × (100 / 50) = 4개로 늘림
 예) 목표 CPU 50%, 현재 평균 CPU 20%, 파드 4개
     → 4 × (20 / 50) = 1.6 → 올림해서 2개로 줄임
HPA의 동작 흐름:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 [파드들] ──CPU 사용량──→ [metrics-server] ──→ ┌──────────────┐
                          (메트릭 수집기)        │ HPA 컨트롤러  │
                                              └──────┬───────┘
                                                     │ 필요한 파드 수 계산
                                                     ▼
                                    ┌──────────────────────────┐
                                    │ 디플로이먼트 / 레플리카셋   │  replicas 변경
                                    └──────────────────────────┘
# hpa.yaml - my-deployment의 파드를 CPU 사용률 기준으로 2~10개 사이에서 조절
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-hpa
spec:
  scaleTargetRef:               # 스케일링 대상
    apiVersion: apps/v1
    kind: Deployment
    name: my-deployment
  minReplicas: 2                # 최소 파드 수
  maxReplicas: 10               # 최대 파드 수
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50  # 평균 CPU 사용률 50% 유지가 목표
실습 6: HPA를 만들었는데 TARGETS가 <unknown>이라면
kubectl apply -f hpa.yaml
kubectl get hpa
# 실제 출력:
# NAME     REFERENCE                  TARGETS              MINPODS   MAXPODS   REPLICAS   AGE
# my-hpa   Deployment/my-deployment   cpu: <unknown>/50%   2         10        3          20s

kubectl describe hpa my-hpa
# 실제 출력 (Conditions 부분):
# AbleToScale    True    SucceededGetScale        the HPA controller was able to get the target's current scale
# ScalingActive  False   FailedGetResourceMetric  ... unable to fetch metrics from resource metrics API:
#                                                 the server could not find the requested resource (get pods.metrics.k8s.io)

kubectl top pods
# 실제 출력:
# error: Metrics API not available

5.8.4. 쿠버네티스에서 파드의 수직 스케일링

파라미터 의미 비유 (식당)
request 리소스의 하한선 (최소 이만큼은 확보) 예약한 좌석 수 — 반드시 비워 둠
limit 리소스의 상한선 (이 이상은 사용 불가) 최대 수용 인원 — 넘으면 입장 불가
실습 7: request/limit 지정하고 노드에서 확인하기
# resources.yaml
apiVersion: v1
kind: Pod
metadata:
  name: resource-demo
spec:
  containers:
  - name: nginx
    image: nginx:stable
    resources:
      requests:          # 하한선: 스케줄러가 이만큼 여유 있는 노드에 배치
        cpu: 250m        # 0.25코어
        memory: 128Mi
      limits:            # 상한선: 이 이상 사용 불가
        cpu: 500m        # 0.5코어
        memory: 256Mi
kubectl apply -f resources.yaml

# QoS 클래스 확인 (아래 표 참고)
kubectl get pod resource-demo -o jsonpath='{.status.qosClass}'
# 실제 출력: Burstable

# 파드가 올라간 노드의 리소스 예약 현황
kubectl describe node desktop-worker
실제 출력 (일부):
Allocatable:              ← 이 노드에서 파드에 줄 수 있는 총량
  cpu:                12
  memory:             7658696Ki
  pods:               110
Allocated resources:      ← 파드들의 request/limit 합계
  Resource           Requests    Limits
  --------           --------    ------
  cpu                350m (2%)   600m (5%)
  memory             178Mi (2%)  306Mi (4%)
QoS 클래스 조건 노드 자원 부족 시 실습에서
Guaranteed 모든 컨테이너가 request = limit 가장 마지막에 종료 -
Burstable request만 있거나 request < limit 중간 resource-demo
BestEffort request·limit 모두 없음 가장 먼저 종료 5.9.4의 my-pod
실습 8: 파드를 재시작하지 않고 리소스 늘리기 (In-place Pod Resize)

예전에는 request/limit을 바꾸려면 파드를 새로 만들어야 했음. 최신 버전에서는 실행 중인 파드의 리소스를 그대로 바꿀 수 있음

# 변경 전 파드의 고유 ID(UID) 확인
kubectl get pod resource-demo -o jsonpath='{.metadata.uid}'
# 실제 출력: fd88b73a-de2f-4ff4-8a02-2a90bde88a64

# CPU request 250m → 400m, limit 500m → 800m 으로 변경
kubectl patch pod resource-demo --subresource resize \
  --patch '{"spec":{"containers":[{"name":"nginx","resources":{"requests":{"cpu":"400m"},"limits":{"cpu":"800m"}}}]}}'
# 실제 출력: pod/resource-demo patched

kubectl get pod resource-demo
# 실제 출력:
# NAME            READY   STATUS    RESTARTS   AGE
# resource-demo   1/1     Running   0          13s     ← RESTARTS 0

kubectl get pod resource-demo -o jsonpath='{.metadata.uid}{"\n"}{.status.containerStatuses[0].resources}'
# 실제 출력:
# fd88b73a-de2f-4ff4-8a02-2a90bde88a64                 ← UID 그대로 = 같은 파드
# {"limits":{"cpu":"800m","memory":"256Mi"},"requests":{"cpu":"400m","memory":"128Mi"}}
2026-10 갱신

  • 매니페스트에서는 requests / limits (복수형)로 씀. request는 엄밀히 "하한선"이라기보다 스케줄러가 노드를 고를 때 기준으로 삼는 예약량임
  • VPA는 지금도 쿠버네티스 본체가 아닌 별도 애드온(kubernetes/autoscaler 저장소)으로 설치함
  • In-place Pod Resize는 v1.35에서 GA가 되었고, 위 실습처럼 v1.36 클러스터에서 기본으로 동작함

5.8.5. 클러스터 자체의 수평 스케일링

실습 9: "리소스 부족으로 파드를 시작하지 못한 경우" 직접 만들기

CPU를 100코어 요청하는 파드를 만들어 봄 (노드 한 대는 12코어)

# too-big.yaml
apiVersion: v1
kind: Pod
metadata:
  name: too-big
spec:
  containers:
  - name: nginx
    image: nginx:stable
    resources:
      requests:
        cpu: "100"       # 100코어 요청
kubectl apply -f too-big.yaml
kubectl get pod too-big
# 실제 출력:
# NAME      READY   STATUS    RESTARTS   AGE
# too-big   0/1     Pending   0          4s        ← 어느 노드에도 배치되지 못함

kubectl describe pod too-big
# 실제 출력 (Events):
# Warning  FailedScheduling  4s  default-scheduler  0/3 nodes are available:
#   1 node(s) had untolerated taint(s), 2 Insufficient cpu. ...
CA의 리소스 사용률 계산식:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

                   요청된(request) CPU 및 메모리 양
  리소스 사용률 = ─────────────────────────────────────
                    사용 가능한(allocatable) CPU 및 메모리 양

  → 일정 임곗값(기본 50%)을 밑도는 상태가 지속(기본 10분)되면 노드 축소
  ※ 실제 사용량이 아니라 request 합계 기준 → 실습 7의 "Allocated resources"가 바로 이 값
HPA와 CA의 연계:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 요청 급증
    │
    ▼
 [HPA] 파드 4개 → 8개로 증가 지시
    │
    ▼
 노드 리소스 부족 → 새 파드 4개가 Pending (실습 9의 상태)
    │
    ▼
 [CA] Pending 파드 감지 → 클라우드에 노드 추가 요청
    │
    ▼
 새 노드에 Pending 파드가 배치되어 실행
구분 HPA VPA CA
대상 파드 파드 노드(클러스터)
방향 수평 (개수) 수직 (크기) 수평 (개수)
조정 항목 레플리카 수 request / limit 노드 수
증가 계기 메트릭이 임곗값 초과 리소스 소비량 리소스 부족으로 파드 시작 불가 (Pending)
필요한 것 metrics-server VPA 애드온 + metrics-server 퍼블릭 클라우드 + CA 설치
용어 정리

  • HPA (Horizontal Pod Autoscaler): 메트릭에 따라 파드 수를 자동 증감하는 기능
  • VPA (Vertical Pod Autoscaler): 리소스 소비량에 따라 파드의 request/limit을 자동 조정하는 기능
  • CA (Cluster Autoscaler): 퍼블릭 클라우드에서 클러스터의 노드 수를 자동 증감하는 기능
  • metrics-server: 파드·노드의 CPU/메모리 사용량을 모아 HPA·kubectl top에 제공하는 구성 요소
  • request / limit: 파드에 할당하는 리소스의 하한선(예약량) / 상한선
  • QoS 클래스: request/limit 지정 방식에 따른 파드 등급. 자원 부족 시 종료 우선순위를 결정
  • Pending: 파드가 생성됐지만 배치될 노드가 정해지지 않은 상태
  • 테인트 (Taint): 노드에 거는 "이 조건이 없는 파드는 받지 않음" 표시

2026-10 갱신

  • AWS(EKS)에서는 CA 대신 Karpenter도 널리 쓰임. 노드 그룹 단위가 아니라 Pending 파드에 맞는 인스턴스 타입을 직접 골라 노드를 띄우므로 더 빠르고 유연함 (v1.0 GA)


5.9. 쿠버네티스의 주요 명령어: 필수 명령어 소개

5.9.1. 선언형 기술로 클러스터 조작

구분 선언형 기술 절차형 기술
작성 내용 기대하는 상태 기대하는 상태로 바꾸기 위한 조작 순서
예시 "파드 이름은 my-pod, 이미지는 nginx:stable" "① 이미지를 받고 ② 컨테이너를 만들고 ③ 시작한다"
비유 택시에서 "강남역이요" (목적지) "직진 후 두 번째 신호에서 우회전..." (경로)
쿠버네티스 ○ 사용 ✗ 불필요

YAML 매니페스트의 기본 구조 (모든 매니페스트 공통):

apiVersion: v1          # ① 이 객체를 다루는 API 버전 (종류마다 정해져 있음)
kind: Pod               # ② 만들 객체의 종류 (Pod, Deployment, Service ...)
metadata:               # ③ 객체를 식별하는 정보
  name: my-pod          #    이름 (같은 종류·네임스페이스 안에서 유일)
  labels:               #    레이블 (5.9.2)
    app: my-app
spec:                   # ④ 기대하는 상태 (종류마다 내용이 다름)
  containers:
  - name: nginx         # "-"는 목록의 한 항목 (컨테이너가 여러 개일 수 있음)
    image: nginx:stable
YAML 초보자가 자주 하는 실수

  • 들여쓰기는 스페이스로만 (탭 문자 사용 불가). 들여쓰기 깊이가 곧 구조임
  • key: value에서 콜론 뒤에 공백 한 칸 필수
  • 목록 항목은 - (하이픈 + 공백)으로 시작

5.9.2. 매니페스트에서 객체를 선택하는 방법

레이블과 레이블 셀렉터:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 셀렉터: app = my-app
        │
        │ 조건에 맞는 객체만 선택
        ▼
 ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
 │ 파드 1           │ │ 파드 2           │ │ 파드 3           │
 │ app: my-app  ✔  │ │ app: my-app  ✔  │ │ app: demo    ✗  │
 │                 │ │                 │ │ tier: backend   │
 └─────────────────┘ └─────────────────┘ └─────────────────┘
실습: 레이블 붙이고 골라내기
# 이미 있는 파드에 레이블 2개 붙이기
kubectl label pod resource-demo app=demo tier=backend
# 실제 출력: pod/resource-demo labeled

# 모든 파드와 레이블 표시
kubectl get pods --show-labels
# 실제 출력:
# NAME                             READY   STATUS    RESTARTS   AGE     LABELS
# client                           1/1     Running   0          4m29s   run=client
# my-deployment-7f7c74cb79-9xsl5   1/1     Running   0          44s     app=my-app,pod-template-hash=7f7c74cb79
# my-deployment-7f7c74cb79-pg5j9   1/1     Running   0          48s     app=my-app,pod-template-hash=7f7c74cb79
# my-deployment-7f7c74cb79-ppsgj   1/1     Running   0          45s     app=my-app,pod-template-hash=7f7c74cb79
# resource-demo                    1/1     Running   0          19s     app=demo,tier=backend

# -l (--selector): 레이블 셀렉터로 골라내기
kubectl get pods -l app=my-app
# 실제 출력: my-deployment-... 3개만 표시

# 여러 조건: 쉼표(,) = AND, in (...) = 목록 중 하나
kubectl get pods -l 'app in (demo),tier=backend'
# 실제 출력: resource-demo 1개만 표시
용어 정리

  • 매니페스트 (Manifest): 클러스터에 기대하는 상태를 YAML/JSON으로 선언한 텍스트 파일
  • 선언형 기술: 기대하는 상태를 적는 방식. 절차는 쿠버네티스가 자동으로 계획
  • 절차형 기술: 원하는 상태가 되기 위한 조작 순서를 적는 방식
  • 레이블 (Label): 객체에 붙이는 키-값 쌍. 하나의 객체에 여러 개 할당 가능
  • 레이블 셀렉터 (Label Selector): 레이블 조건으로 객체를 선택하는 방법

5.9.3. 쿠버네티스 명령어를 사용할 수 있는 환경

환경 설치 방법
리눅스 (CentOS, 우분투 등) yum, apt 같은 패키지 관리 시스템으로 설치
윈도 / 맥OS 도커 데스크톱(2018년 7월 이후 정식 버전)에 포함 → 설정 화면에서 쿠버네티스 활성화
# 설치가 잘 됐는지 확인하는 첫 명령어
kubectl version
# 실제 출력:
# Client Version: v1.34.0      ← 내 PC의 kubectl
# Kustomize Version: v5.7.1
# Server Version: v1.36.1      ← 클러스터(API 서버)

kubectl cluster-info
# 실제 출력:
# Kubernetes control plane is running at https://127.0.0.1:62824
# CoreDNS is running at https://127.0.0.1:62824/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
2026-10 갱신

  • CentOS Linux는 지원이 종료(CentOS 7: 2024년 6월)되어 Rocky Linux, AlmaLinux 등이 대안
  • 리눅스 패키지 저장소는 예전 apt.kubernetes.io / yum.kubernetes.io가 동결되고 pkgs.k8s.io 로 바뀜
  • 로컬 학습용으로는 도커 데스크톱 외에 minikube, kind 도 많이 사용함. 최신 도커 데스크톱은 kind 방식으로 멀티 노드 클러스터도 만들 수 있음 (이 노트의 실습 환경)

5.9.4. 파드의 생성/확인/명령어 실행/변경/삭제

# pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-pod              # 파드 이름
  labels:
    app: my-app             # 레이블 (키: app, 값: my-app)
spec:
  containers:
  - name: nginx
    image: nginx:stable     # 실행할 컨테이너 이미지
실습 10: 파드 생성 → 확인 → 명령 실행 → 변경 → 삭제

① 생성

kubectl apply -f pod.yaml
# 실제 출력: pod/my-pod created

kubectl get pods                # 바로 확인
# 실제 출력:
# NAME     READY   STATUS              RESTARTS   AGE
# my-pod   0/1     ContainerCreating   0          0s

kubectl get pods                # 15초 후 다시 확인
# 실제 출력:
# NAME     READY   STATUS    RESTARTS   AGE
# my-pod   1/1     Running   0          15s

kubectl get pods 출력 읽는 법:

열 의미 예시 해석
NAME 파드 이름 my-pod
READY 준비된 컨테이너 수 / 전체 컨테이너 수 1/1 = 1개 중 1개 준비 완료
STATUS 파드 상태 ContainerCreating(이미지 받는 중) → Running(실행 중)
RESTARTS 컨테이너 재시작 횟수 0 = 한 번도 죽지 않음
AGE 만들어진 지 지난 시간 15s

② 자세히 확인 (-o wide, describe)

kubectl get pods -o wide --show-labels
# 실제 출력:
# NAME     READY   STATUS    RESTARTS   AGE   IP           NODE             ...   LABELS
# my-pod   1/1     Running   0          15s   10.244.2.2   desktop-worker   ...   app=my-app

kubectl describe pod my-pod      # 파드의 모든 정보 + 이벤트 기록
실제 출력 (맨 아래 Events 부분):
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  30s   default-scheduler  Successfully assigned default/my-pod to desktop-worker
  Normal  Pulling    29s   kubelet            Pulling image "nginx:stable"
  Normal  Pulled     16s   kubelet            Successfully pulled image "nginx:stable" in 13.073s ... Image size: 63608941 bytes.
  Normal  Created    15s   kubelet            Container created
  Normal  Started    15s   kubelet            Container started

③ 파드 안에서 명령 실행 (exec)

kubectl exec -it my-pod -- echo Hello Kubernetes
# 실제 출력: Hello Kubernetes

kubectl exec my-pod -- nginx -v      # 컨테이너 안의 nginx 버전 확인
# 실제 출력: nginx version: nginx/1.30.6

kubectl exec my-pod -- hostname      # 컨테이너의 호스트 이름 = 파드 이름
# 실제 출력: my-pod
Unable to use a TTY 경고

스크립트나 일부 터미널에서 -it를 쓰면 Unable to use a TTY - input is not a terminal ... 경고가 나올 수 있음. 명령 결과에는 영향 없음. 한 줄 명령이면 -it를 빼면 됨

④ 변경

# pod.yaml을 고치지 않고 다시 적용
kubectl apply -f pod.yaml
# 실제 출력: pod/my-pod unchanged      ← 기대 상태와 같으니 아무것도 안 함 (선언형)

# 이미지를 nginx:stable → nginx:stable-alpine 으로 바꾼 매니페스트 적용
kubectl apply -f pod-v2.yaml
# 실제 출력: pod/my-pod configured

kubectl get pods
# 실제 출력:
# NAME     READY   STATUS    RESTARTS      AGE
# my-pod   1/1     Running   1 (15s ago)   42s     ← 컨테이너가 새 이미지로 재시작됨
파드는 대부분의 항목을 바꿀 수 없음

실행 중인 파드에 포트(ports)를 추가해 다시 적용하면 오류가 남

The Pod "my-pod" is invalid: spec: Forbidden: pod updates may not change fields other than
`spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds`,
`spec.tolerations` (only additions to existing tolerations), `spec.terminationGracePeriodSeconds` ...

→ 파드는 이미지 등 일부 항목만 바꿀 수 있음. 그 밖의 변경은 파드를 지우고 새로 만들어야 함. 그래서 실무에서는 파드를 직접 만들지 않고 디플로이먼트(5.9.6)가 파드를 새로 만들어 교체하게 함

⑤ 삭제

kubectl delete -f pod.yaml
# 출력: pod "my-pod" deleted

5.9.5. 레플리카셋의 생성/확인/변경/삭제

# replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: my-replicaset
spec:
  replicas: 3               # 유지할 파드 수
  selector:
    matchLabels:
      app: my-app           # 이 레이블을 가진 파드를 "내 것"으로 관리 (레이블 셀렉터)
  template:                 # ↓ 여기부터 파드 매니페스트와 거의 같음 (파드를 찍어 내는 틀)
    metadata:
      labels:
        app: my-app         # 틀로 만든 파드에 붙일 레이블 (selector와 일치해야 함)
    spec:
      containers:
      - name: nginx
        image: nginx:stable
실습 11: 레플리카셋 생성과 "놀라운" 결과

실습 10의 my-pod(레이블 app=my-app)를 지우지 않은 상태에서 레플리카셋을 만들어 봄

kubectl apply -f replicaset.yaml
# 실제 출력: replicaset.apps/my-replicaset created

kubectl get replicasets
# 실제 출력:
# NAME            DESIRED   CURRENT   READY   AGE
# my-replicaset   3         3         2       3s

kubectl get pods --show-labels
# 실제 출력:
# NAME                  READY   STATUS              RESTARTS      AGE   LABELS
# my-pod                1/1     Running             1 (24s ago)   51s   app=my-app
# my-replicaset-6r4vr   0/1     ContainerCreating   0             3s    app=my-app
# my-replicaset-fbqll   1/1     Running             0             3s    app=my-app
# 각 파드의 주인(ownerReferences) 확인
kubectl get pods -o custom-columns=NAME:.metadata.name,OWNER:.metadata.ownerReferences[0].name
# 실제 출력:
# NAME                  OWNER
# my-pod                my-replicaset      ← 따로 만든 파드인데 주인이 생김
# my-replicaset-6r4vr   my-replicaset
# my-replicaset-fbqll   my-replicaset

kubectl get replicasets 출력 읽는 법:

열 의미
DESIRED 원하는 파드 수 (replicas)
CURRENT 현재 만들어진 파드 수
READY 그중 준비 완료된 파드 수
실습 12: 자동 복구(셀프 힐링)와 스케일링
# 파드 하나를 일부러 삭제
kubectl delete pod my-replicaset-fbqll
kubectl get pods
# 실제 출력:
# NAME                  READY   STATUS    RESTARTS      AGE
# my-pod                1/1     Running   1 (31s ago)   58s
# my-replicaset-6r4vr   1/1     Running   0             10s
# my-replicaset-qzdsv   1/1     Running   0             2s     ← 2초 전에 새로 생김
# replicas: 3 → 5 로 고친 매니페스트 적용 (변경 방법은 파드와 동일)
kubectl apply -f rs5.yaml
# 실제 출력: replicaset.apps/my-replicaset configured

kubectl get rs
# 실제 출력:
# NAME            DESIRED   CURRENT   READY   AGE
# my-replicaset   5         5         5       22s

kubectl get pods -o wide        # NODE 열만 보면
# my-replicaset-6r4vr  ... desktop-worker2
# my-replicaset-8b45p  ... desktop-worker
# my-replicaset-qzdsv  ... desktop-worker2
# my-replicaset-tkmwz  ... desktop-worker
# my-pod               ... desktop-worker
# 삭제 (방법은 파드와 동일)
kubectl delete -f replicaset.yaml
# 실제 출력: replicaset.apps "my-replicaset" deleted from default namespace

kubectl get pods
# 실제 출력: No resources found in default namespace.

5.9.6. 디플로이먼트의 생성/확인/변경/삭제

# deployment.yaml
apiVersion: apps/v1
kind: Deployment            # ← 레플리카셋과의 차이는 사실상 이 줄
metadata:
  name: my-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: nginx
        image: nginx:stable
실습 13: 디플로이먼트 생성과 3단 구조 확인
kubectl apply -f deployment.yaml
# 실제 출력: deployment.apps/my-deployment created

kubectl rollout status deployment/my-deployment     # 배포가 끝날 때까지 지켜보기
# 실제 출력:
# Waiting for deployment "my-deployment" rollout to finish: 0 of 3 updated replicas are available...
# Waiting for deployment "my-deployment" rollout to finish: 1 of 3 updated replicas are available...
# Waiting for deployment "my-deployment" rollout to finish: 2 of 3 updated replicas are available...
# deployment "my-deployment" successfully rolled out

kubectl get deployments
# 실제 출력:
# NAME            READY   UP-TO-DATE   AVAILABLE   AGE
# my-deployment   3/3     3            3           1s

kubectl get rs
# 실제 출력:
# NAME                       DESIRED   CURRENT   READY   AGE
# my-deployment-5f9c57bbf7   3         3         3       1s

kubectl get pods -o wide
# 실제 출력:
# NAME                             READY   STATUS    RESTARTS   AGE   IP           NODE
# my-deployment-5f9c57bbf7-9w5sz   1/1     Running   0          1s    10.244.1.4   desktop-worker2
# my-deployment-5f9c57bbf7-c4wvz   1/1     Running   0          1s    10.244.2.6   desktop-worker
# my-deployment-5f9c57bbf7-k5slg   1/1     Running   0          1s    10.244.2.7   desktop-worker

kubectl get deployments 출력 읽는 법:

열 의미
READY 준비된 파드 수 / 원하는 파드 수
UP-TO-DATE 최신 template으로 만들어진 파드 수 (레플리카셋에는 없는 열)
AVAILABLE 사용자에게 서비스 가능한 파드 수

이름에 숨은 3단 구조:

디플로이먼트 → 레플리카셋 → 파드의 이름 규칙:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  my-deployment                        ← 디플로이먼트 (내가 지은 이름)
  my-deployment-5f9c57bbf7             ← 레플리카셋 (+ template의 해시값)
  my-deployment-5f9c57bbf7-9w5sz       ← 파드 (+ 랜덤 5글자)

  5f9c57bbf7 = pod-template-hash
  → template 내용(이미지 등)이 바뀌면 해시가 바뀌고 → 새 레플리카셋이 만들어짐 (5.10.4)

5.9.7. 서비스의 생성/확인/변경/삭제

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app             # 이 레이블을 가진 파드로 전송
  ports:
  - name: http              # 규칙 이름
    protocol: TCP
    port: 8080              # 서비스가 받는 포트 (손님이 거는 번호)
    targetPort: 80          # 파드로 전달할 포트 (nginx가 듣고 있는 번호)
실습 14: 서비스 생성과 확인
kubectl apply -f service.yaml
# 실제 출력: service/my-service created

kubectl get services
# 실제 출력:
# NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
# kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP    14d
# my-service   ClusterIP   10.96.103.142   <none>        8080/TCP   0s
열 의미
TYPE 서비스 유형. type을 안 적으면 기본값 ClusterIP (클러스터 내부 전용, 5-1의 5.5.3)
CLUSTER-IP 이 서비스용으로 발행된 가상 IP 주소
EXTERNAL-IP 외부 공개 IP. ClusterIP 유형이라 <none>
PORT(S) 서비스가 받는 포트
kubectl describe service my-service
실제 출력:
Name:                     my-service
Namespace:                default
Selector:                 app=my-app                    ← 이 레이블의 파드로 보냄
Type:                     ClusterIP
IP:                       10.96.103.142                 ← 가상 IP
Port:                     http  8080/TCP                ← 규칙 이름과 받는 포트
TargetPort:               80/TCP                        ← 파드 쪽 포트
Endpoints:                10.244.1.4:80,10.244.2.6:80,10.244.2.7:80   ← 실제 파드 3개의 IP:포트
Session Affinity:         None

주요 명령어 정리:

작업 명령어 비고
생성 / 변경 kubectl apply -f <매니페스트> 출력: created / configured / unchanged
목록 확인 kubectl get <종류> pods(po), replicasets(rs), deployments(deploy), services(svc)
자세히 확인 kubectl get <종류> -o wide IP, 노드 등 추가 열
상세 + 이벤트 kubectl describe <종류> <이름> 문제 해결의 출발점
레이블로 선택 kubectl get <종류> -l <조건> -l app=my-app
명령 실행 kubectl exec [-it] <파드> -- <명령> 셸 접속은 -it ... -- sh
파드 수 변경 kubectl scale deploy <이름> --replicas=N 실습 5
배포 진행 확인 kubectl rollout status deploy/<이름> 실습 13
삭제 kubectl delete -f <매니페스트> 또는 kubectl delete <종류> <이름>
리소스 매니페스트의 관계:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 service.yaml
   selector: app=my-app ─────────────┐ 레이블로 대상 파드 선택
                                     ▼
 deployment.yaml ──→ 레플리카셋 ──→ 파드 ×3 (labels: app=my-app)
   template 안에                     ↑
   pod.yaml 내용이 들어 있음 ──────────┘

5.10. 자동 빌드: 이미지 생성부터 배포까지 절차를 자동화

5.10.1. 자동 빌드란

5.10.2. 자동 배포란

왜 자동화해야 할까?

사람이 매번 손으로 하면 순서를 빠뜨리거나(테스트 생략), 환경마다 다르게 하거나(내 PC에서는 됐는데...), 배포가 무서워서 드물게 하게 됨. 자동화하면 같은 절차가 매번 똑같이 실행되므로 작은 변경을 자주, 안전하게 내보낼 수 있음. 이 흐름을 흔히 CI/CD라고 부름

용어 정리

  • 자동 빌드: 컴파일·라이브러리 설치·테스트 등 프로그램을 사용 가능한 상태로 만드는 절차의 자동화 (CI, Continuous Integration에 대응)
  • 자동 배포: 서버 업로드·DB 변경 등 릴리스 절차의 자동화 (CD, Continuous Delivery/Deployment에 대응)
  • 아티팩트 (Artifact): 자동 빌드의 결과물 (실행 파일, 컨테이너 이미지 등)

5.10.3. 이미지 생성부터 배포까지의 절차

단계 작업 내용 대표 명령
1 이미지 생성 도커 파일(Dockerfile)로 컨테이너 이미지를 생성 docker build
2 레지스트리 등록 이미지를 레지스트리에 푸시해 운영 환경에서 가져올 준비 docker push
3 배포 업데이트 디플로이먼트 매니페스트의 이미지 버전을 변경하고 kubectl로 적용 → 이전 파드 중지 + 새 파드 시작 kubectl apply
이미지 생성부터 배포까지 자동화:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[개발자]
    │ git push (트리거)
    ▼
[빌드용 서버] ──────────────────────────────────────────┐
    │                                                  │
    │ 1. 이미지 생성                                     │
    │    docker build -t my-app:v2 .                   │
    ▼                                                  │
    │ 2. 레지스트리 등록                                  │ 자동화
    │    docker push registry.example.com/my-app:v2    │
    ▼                                                  │
    │ 3. 배포 업데이트                                    │
    │    매니페스트 image: my-app:v1 → v2               │
    │    kubectl apply -f deployment.yaml              │
    ▼ ─────────────────────────────────────────────────┘
[쿠버네티스 클러스터]
    └─ 이전 파드(v1) 중지 + 새 파드(v2) 시작   ← 이 부분을 5.10.4에서 실습

5.10.4. 새로운 파드로 변경하는 방법

구분 재생성 (Recreate) 롤링 업데이트 (RollingUpdate)
동작 기존 파드를 모두 중지한 후 새 파드 시작 기존 파드를 줄이는 동시에 새 파드를 늘림
다운타임 일시적으로 모든 파드가 다운 모든 파드가 다운되지는 않음
버전 혼재 기존·새 파드가 섞이지 않음 일시적으로 기존·새 파드가 섞임
기본값 - ○ (기본 적용)
언제 쓰나 두 버전이 동시에 돌면 안 될 때 (DB 구조가 바뀌는 경우 등) 대부분의 웹 서비스
업데이트 전략 비교 (파드 3개):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[재생성]
 v1 v1 v1  →  (전부 중지: 서비스 중단)  →  v2 v2 v2

[롤링 업데이트]
 v1 v1 v1  →  v1 v1 v2  →  v1 v2 v2  →  v2 v2 v2
              └──── 서비스는 계속, 버전이 섞인 구간 ────┘
실습 15: 롤링 업데이트를 1초 단위로 관찰하기

실습 13의 my-deployment(nginx:stable, 파드 3개)의 이미지를 nginx:stable-alpine으로 바꾸고, 레플리카셋 2개의 파드 수 변화를 1초마다 찍어 봄

# 이미지 변경 (매니페스트를 고쳐 apply 하는 것과 같은 효과)
kubectl set image deployment/my-deployment nginx=nginx:stable-alpine
# 실제 출력: deployment.apps/my-deployment image updated

kubectl get rs -l app=my-app      # 1초 간격으로 반복 실행
실제 출력 (DESIRED / CURRENT / READY):
                       기존 RS (5f9c57bbf7)    새 RS (6bf9d78567)
  t+0s                       3 / 3 / 3              1 / 1 / 0     ← 새 파드 1개 생성 시작
  t+1s                       2 / 2 / 2              2 / 2 / 1     ← 기존 1개 줄이고 새 것 1개 더
  t+4s                       1 / 1 / 1              3 / 3 / 2
  t+5s                       0 / 0 / 0              3 / 3 / 3     ← 교체 완료
kubectl get rs -l app=my-app
# 실제 출력 (완료 후):
# NAME                       DESIRED   CURRENT   READY   AGE
# my-deployment-5f9c57bbf7   0         0         0       2m54s    ← 지워지지 않고 0개로 남음
# my-deployment-6bf9d78567   3         3         3       10s

kubectl describe deployment my-deployment
실제 출력 (일부):
StrategyType:           RollingUpdate
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Events:
  Reason             From                   Message
  ScalingReplicaSet  deployment-controller  Scaled up replica set my-deployment-6bf9d78567 from 0 to 1
  ScalingReplicaSet  deployment-controller  Scaled down replica set my-deployment-5f9c57bbf7 from 3 to 2
  ScalingReplicaSet  deployment-controller  Scaled up replica set my-deployment-6bf9d78567 from 1 to 2
  ScalingReplicaSet  deployment-controller  Scaled down replica set my-deployment-5f9c57bbf7 from 2 to 1
  ScalingReplicaSet  deployment-controller  Scaled up replica set my-deployment-6bf9d78567 from 2 to 3
  ScalingReplicaSet  deployment-controller  Scaled down replica set my-deployment-5f9c57bbf7 from 1 to 0
실습 16: 롤백 — 이전 버전으로 되돌리기
kubectl rollout history deployment/my-deployment
# 실제 출력:
# REVISION  CHANGE-CAUSE
# 1         <none>          ← nginx:stable
# 2         <none>          ← nginx:stable-alpine

kubectl rollout undo deployment/my-deployment
# 실제 출력: deployment.apps/my-deployment rolled back

kubectl get rs -l app=my-app
# 실제 출력:
# NAME                       DESIRED   CURRENT   READY   AGE
# my-deployment-5f9c57bbf7   3         3         3       3m2s     ← 남겨 둔 기존 RS가 다시 3개로
# my-deployment-6bf9d78567   0         0         0       18s
실습 17: 재생성(Recreate) 전략 관찰
# 업데이트 전략을 Recreate로 변경
kubectl patch deployment my-deployment -p '{"spec":{"strategy":{"type":"Recreate","rollingUpdate":null}}}'
kubectl set image deployment/my-deployment nginx=nginx:stable-alpine
kubectl get pods -l app=my-app      # 1초 간격으로 반복 실행
실제 출력:
== t+0s
my-deployment-5f9c57bbf7-2d7m8   1/1   Terminating        ← 기존 3개 동시에 종료 중
my-deployment-5f9c57bbf7-c2kmt   1/1   Terminating
my-deployment-5f9c57bbf7-pbjt9   1/1   Terminating
== t+1s
my-deployment-6bf9d78567-4fltv   0/1   ContainerCreating  ← 기존이 다 사라진 뒤 새 3개 생성
my-deployment-6bf9d78567-jjg48   0/1   ContainerCreating
my-deployment-6bf9d78567-z28qr   0/1   ContainerCreating
== t+2s
my-deployment-6bf9d78567-4fltv   1/1   Running
my-deployment-6bf9d78567-jjg48   1/1   Running
my-deployment-6bf9d78567-z28qr   1/1   Running
실습 18: 업데이트 중 요청이 실패하는지 직접 세어 보기

업데이트를 하는 동안 client 파드에서 0.2초마다 서비스에 요청을 보내 성공·실패 횟수를 셈

# 0.2초 간격 요청을 60번 보내며 업데이트 실행 (업데이트는 2초 뒤 시작)
kubectl exec client -- sh -c 'ok=0; ng=0; i=0; while [ $i -lt 60 ]; do
  if curl -s -m 1 -o /dev/null http://my-service:8080; then ok=$((ok+1)); else ng=$((ng+1)); fi
  i=$((i+1)); sleep 0.2; done; echo "성공=$ok 실패=$ng"'
조건 실제 결과 해석
Recreate 성공=54 실패=6 모든 파드가 사라진 약 1~2초 동안 요청 실패 (다운타임)
RollingUpdate (기본 설정만) 성공=59 실패=1 거의 무중단이지만 1건 실패
RollingUpdate + readinessProbe + preStop 성공=100 실패=0 완전 무중단
# deployment.yaml - containers 항목에 추가
    readinessProbe:          # 진짜 응답할 수 있을 때만 요청 받기 (5.7.4)
      httpGet:
        path: /
        port: 80
      periodSeconds: 2
    lifecycle:
      preStop:               # 종료 신호를 받으면 5초 기다렸다가 종료
        sleep:               # → 그 사이 서비스 목록에서 먼저 빠짐
          seconds: 5
# 참고: 업데이트 전략과 세부 값 지정 방법
spec:
  strategy:
    type: RollingUpdate     # 또는 Recreate
    rollingUpdate:
      maxUnavailable: 25%   # 업데이트 중 줄어들 수 있는 최대 파드 수 (기본 25%)
      maxSurge: 25%         # 업데이트 중 추가로 늘어날 수 있는 최대 파드 수 (기본 25%)

5.10.5. 자동 빌드/배포를 지원하는 도구

도구 개발 특징
Skaffold 구글 쿠버네티스 특화. 프로그램 변경을 감지해 이미지 생성부터 배포까지 자동 수행
젠킨스 X (Jenkins X) - 쿠버네티스 전문 자동 빌드/배포 도구
스피내커 (Spinnaker) 넷플릭스 자동 배포 도구. 배포 방법을 더욱 세밀하게 설정 가능 (카나리 배포 등)
용어 정리

  • 트리거 (Trigger): 자동화 절차를 시작시키는 이벤트 (예: git push)
  • 업데이트 전략: 기존 파드를 새 파드로 바꾸는 방법. 재생성과 롤링 업데이트가 있음
  • 재생성 (Recreate): 기존 파드를 모두 중지한 뒤 새 파드를 시작하는 전략
  • 롤링 업데이트 (Rolling Update): 기존 파드를 줄이면서 새 파드를 늘리는 전략. 디플로이먼트의 기본값
  • maxSurge / maxUnavailable: 롤링 업데이트 중 추가로 늘어날 수 있는 / 줄어들 수 있는 최대 파드 수
  • 롤백 (Rollback): 이전 버전으로 되돌리는 것. kubectl rollout undo
  • preStop 훅: 컨테이너가 종료되기 직전에 실행되는 동작

2026-10 갱신

  • 현재 쿠버네티스 배포 자동화는 GitOps 방식이 주류임. git 저장소의 매니페스트를 "기대하는 상태"로 두고, 클러스터 안의 도구가 이를 감시해 자동으로 맞춤 (5.9.1의 선언형 개념을 배포까지 확장한 것)
    • 대표 도구: Argo CD, Flux (둘 다 CNCF 졸업 프로젝트)
  • 빌드(CI) 쪽은 GitHub Actions, GitLab CI 등이 널리 쓰이며, 젠킨스 X는 원서 시점보다 사용이 크게 줄었음
  • 실습 18의 preStop에 쓴 sleep 동작은 최신 버전에서 지원하는 문법임. 예전에는 exec: command: ["sleep", "5"]처럼 컨테이너 안의 sleep 명령을 실행했음


5.11. 멀티 클라우드: 여러 클라우드에서 쿠버네티스 사용

5.11.1. 멀티 클라우드란

구분 대상 예시
퍼블릭 클라우드 일반인 대상으로 제공 아마존 웹 서비스(AWS), 구글 클라우드 플랫폼(GCP) 등
프라이빗 클라우드 특정 개인이나 조직에 제공 사내 전용 클라우드 등

5.11.2. 멀티 클라우드의 필요성

단일 클라우드 vs 멀티 클라우드:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[단일 클라우드]                    [멀티 클라우드]

 ┌──────────┐                    ┌──────────┐   ┌──────────┐
 │ 클라우드 A │ ✗ 장애              │ 클라우드 A │   │ 클라우드 B │
 │  시스템    │                    │  시스템    │   │  시스템    │
 └──────────┘                    └──────────┘   └──────────┘
      │                               ✗ 장애 → 분리     │
      ▼                                                ▼
  서비스 전체 중단                             B에서 서비스 계속

5.11.3. 쿠버네티스를 사용한 멀티 클라우드

쿠버네티스가 클라우드 간 차이를 흡수:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

          [같은 매니페스트 + kubectl]
                     │
     ┌───────────────┼───────────────┐
     ▼               ▼               ▼
 ┌────────┐     ┌────────┐     ┌──────────┐
 │  EKS   │     │  GKE   │     │ 온프레미스 │
 │ (AWS)  │     │ (GCP)  │     │ 쿠버네티스 │
 └────────┘     └────────┘     └──────────┘
   클라우드마다 다른 부분은 쿠버네티스 아래로 감춰짐
kubectl 하나로 여러 클러스터 다루기: 컨텍스트

kubectl은 컨텍스트(context) 를 바꿔 가며 여러 클러스터를 조작함. 실습 처음에 본 kubectl config get-contexts의 목록이 바로 그것

kubectl config get-contexts                 # 등록된 클러스터 목록 (* = 현재)
kubectl config use-context docker-desktop   # 조작할 클러스터 전환

멀티 클라우드 환경이라면 aws-prod, gcp-prod처럼 컨텍스트를 여러 개 두고 같은 매니페스트를 각각 적용할 수 있음. 엉뚱한 클러스터에 적용하는 실수를 막으려면 명령 전 현재 컨텍스트를 확인하는 습관이 중요함

5.11.4. 멀티 클라우드 구축을 지원하는 도구

용어 정리

  • 멀티 클라우드 (Multi Cloud): 여러 클라우드를 결합해 구성한 환경
  • 퍼블릭 클라우드: 일반인 대상으로 제공되는 클라우드 (AWS, GCP, Azure 등)
  • 프라이빗 클라우드: 특정 개인이나 조직 전용으로 제공되는 클라우드
  • 벤더 종속 (Vendor Lock-in): 특정 사업자의 고유 기능에 묶여 다른 곳으로 옮기기 어려워지는 상태
  • 컨텍스트 (Context): kubectl이 어느 클러스터에 어떤 사용자로 접속할지 묶어 둔 설정
  • Rancher: 여러 클라우드의 쿠버네티스 클러스터를 구축·관리하는 컨테이너 관리 플랫폼

2026-10 갱신

  • Rancher Labs는 2020년 SUSE에 인수되어 현재 Rancher는 SUSE가 개발함
  • 그 밖의 멀티 클러스터 관리 도구: Google Anthos(GKE Enterprise), Azure Arc, Cluster API(클러스터 생성 자체를 선언형으로 관리), Karmada(여러 클러스터에 워크로드 분산) 등


5.12. 쿠버네티스의 아키텍처: 각 노드에서 동작하는 구성 요소

5.12.1. 쿠버네티스 아키텍처

초보자를 위한 비유: 식당

  • 마스터(컨트롤 플레인) = 홀 매니저 사무실. 주문을 받고(apiserver), 장부에 적고(etcd), 어느 주방에 맡길지 정하고(scheduler), 주문대로 나오는지 계속 확인함(controller-manager)
  • 워커 = 주방. 주방장(kubelet)이 주문서(PodSpec)대로 요리(컨테이너)를 만들고, 서빙 담당(kube-proxy)이 손님 요청을 알맞은 테이블로 안내함

# 이 클러스터의 노드와 역할
kubectl get nodes
# 실제 출력:
# NAME                    STATUS   ROLES           AGE   VERSION
# desktop-control-plane   Ready    control-plane   14d   v1.36.1    ← 마스터 (컨트롤 플레인)
# desktop-worker          Ready    <none>          14d   v1.36.1    ← 워커
# desktop-worker2         Ready    <none>          14d   v1.36.1    ← 워커

kubectl describe node desktop-control-plane | grep Taints
# 실제 출력:
# Taints:             node-role.kubernetes.io/control-plane:NoSchedule

5.12.2. 쿠버네티스 클러스터를 조작하는 방법

방법 형태 특징
kubectl 명령어 명령줄 인터페이스(CLI) 명령을 실행해 클러스터를 조작
대시보드 웹 기반 GUI 클러스터 조작 + 파드 CPU 사용률 등 통계를 그래프로 표시
쿠버네티스 API API 마스터에 요청을 보내 조작. 프로그램에서 접근할 때 적합
실습 19: kubectl이 실제로 API를 호출하는 모습 보기
# -v=6: kubectl이 보내는 HTTP 요청을 로그로 보여 줌
kubectl get pods -l app=my-app -v=6
실제 출력 (로그 줄):
"Response" verb="GET" url="https://127.0.0.1:62824/api/v1/namespaces/default/pods?labelSelector=app%3Dmy-app&limit=500" status="200 OK"
# kubectl 없이 같은 API를 직접 호출해 보기 (결과는 JSON)
kubectl get --raw "/api/v1/namespaces/default/pods?labelSelector=app%3Dmy-app"
# 실제 출력 (앞부분):
# {"kind":"PodList","apiVersion":"v1","metadata":{"resourceVersion":"92475"},
#  "items":[{"metadata":{"name":"my-deployment-7f7c74cb79-9xsl5", ... "labels":{"app":"my-app", ...
2026-10 갱신

  • 공식 웹 UI였던 Kubernetes Dashboard는 유지보수가 종료(아카이브)되었고, SIG UI는 대안으로 Headlamp를 권장함. 그 밖에 데스크톱 도구 Lens, 터미널 UI k9s 등도 많이 쓰임. Docker Desktop 자체 화면에서도 파드·서비스 목록을 볼 수 있음

5.12.3. 마스터의 구성 요소

구성 요소 역할
kube-apiserver 사용자와 워커가 클러스터와 상호 작용하는 엔드포인트(창구) 제공
etcd 데이터를 저장하는 키-값 저장소. 여러 노드로 구성되어 일부가 고장 나도 데이터 손실·불일치가 없음
kube-scheduler 파드를 시작할 노드를 계획. 필요한 리소스 등 다양한 조건을 고려해 최적의 노드 결정
kube-controller-manager 디플로이먼트 컨트롤러 등 여러 컨트롤러의 실행·정지를 관리해 클러스터를 정상 상태로 유지
실습 20: 마스터의 구성 요소도 사실은 파드다
# kube-system 네임스페이스 = 쿠버네티스 자신의 구성 요소가 사는 곳
kubectl get pods -n kube-system \
  -o custom-columns=NAME:.metadata.name,OWNER:.metadata.ownerReferences[0].kind,NODE:.spec.nodeName
실제 출력:
NAME                                            OWNER        NODE
etcd-desktop-control-plane                      Node         desktop-control-plane   ┐
kube-apiserver-desktop-control-plane            Node         desktop-control-plane   │ 마스터 구성 요소
kube-controller-manager-desktop-control-plane   Node         desktop-control-plane   │ (5.12.3)
kube-scheduler-desktop-control-plane            Node         desktop-control-plane   ┘
kube-proxy-gpv8r                                DaemonSet    desktop-worker          ┐ 워커 구성 요소 (5.12.4)
kube-proxy-hdbpr                                DaemonSet    desktop-control-plane   │ 노드마다 1개씩
kube-proxy-jvtsc                                DaemonSet    desktop-worker2         ┘
kindnet-kg5rv                                   DaemonSet    desktop-worker          ┐ CNI 플러그인
kindnet-kk4jr                                   DaemonSet    desktop-control-plane   │ (5-1의 플라넬과
kindnet-n7d4w                                   DaemonSet    desktop-worker2         ┘  같은 역할)
coredns-589f44dc88-jv6r9                        ReplicaSet   desktop-control-plane   ┐ 클러스터 DNS
coredns-589f44dc88-pxbhk                        ReplicaSet   desktop-control-plane   ┘ (실습 1의 이름 확인)
# etcd 안에 실제로 무엇이 저장되어 있는지 키 목록 보기
kubectl exec -n kube-system etcd-desktop-control-plane -- etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  get /registry/ --prefix --keys-only | grep "default/my-"
# 실제 출력:
# /registry/deployments/default/my-deployment
# /registry/endpointslices/default/my-service-4nkk7
# ...
# 리더 선출: 컨트롤러 매니저·스케줄러를 여러 개 띄워도 일은 한 개만 하도록
kubectl get leases -n kube-system
# 실제 출력:
# NAME                      HOLDER
# kube-controller-manager   desktop-control-plane_c1d0ded8-...
# kube-scheduler            desktop-control-plane_399d5d47-...

5.12.4. 워커의 구성 요소

구성 요소 역할
kubelet 마스터에서 PodSpec(파드 정의)을 가져와 컨테이너 런타임 인터페이스(CRI) 로 컨테이너를 실행·정지·감시 (→ 5.7.4 헬스 체크)
kube-proxy 마스터에서 네트워크 구성을 가져와 통신의 접근 중계·주소 변환 규칙을 변경 (→ 5.7.3 로드 밸런싱, 실습 2의 iptables 규칙)
컨테이너 런타임 실제로 컨테이너를 실행 (이 클러스터는 containerd 2.3.1)
kubectl get nodes -o wide
# 실제 출력 (일부 열):
# NAME             ...  OS-IMAGE                       KERNEL-VERSION                     CONTAINER-RUNTIME
# desktop-worker   ...  Debian GNU/Linux 13 (trixie)   6.6.87.2-microsoft-standard-WSL2   containerd://2.3.1
쿠버네티스 아키텍처 전체 구성 (이 실습 클러스터 기준):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 [kubectl]   [대시보드]   [프로그램]
     └────────────┼────────────┘
                  │ 쿠버네티스 API (HTTPS, 실습 19)
                  ▼
┌──────────────── desktop-control-plane (마스터) ────────────────┐
│                                                               │
│   ┌────────────────┐        ┌──────────┐                      │
│   │ kube-apiserver │ ←────→ │   etcd   │                      │
│   │  (프론트엔드)    │        │ (백엔드)   │                      │
│   └───────┬────────┘        └──────────┘                      │
│           │                                                   │
│     ┌─────┴──────────────┐                                    │
│     ▼                    ▼                                    │
│ ┌────────────────┐ ┌─────────────────────────┐                │
│ │ kube-scheduler │ │ kube-controller-manager │                │
│ │ 파드 배치 노드 결정 │ │ 컨트롤러 실행·관리          │                │
│ └────────────────┘ └─────────────────────────┘                │
└───────────────────────────────┬───────────────────────────────┘
                                │ PodSpec · 네트워크 구성
           ┌────────────────────┴────────────────────┐
           ▼                                         ▼
┌──────── desktop-worker ────────┐    ┌──────── desktop-worker2 ───────┐
│ [kubelet]    [kube-proxy]     │    │ [kubelet]    [kube-proxy]     │
│     │ CRI        │ NAT 규칙    │    │     │ CRI        │ NAT 규칙    │
│     ▼            ▼            │    │     ▼            ▼            │
│ [containerd]  [iptables]      │    │ [containerd]  [iptables]      │
│     │                         │    │     │                         │
│  [파드] [파드]                  │    │  [파드] [파드]                  │
└───────────────────────────────┘    └───────────────────────────────┘
실습 21: 디플로이먼트 하나를 만들면 누가 무엇을 하나

새 디플로이먼트를 만들고, 그때 발생한 이벤트를 보고한 구성 요소(SOURCE) 와 함께 출력함

kubectl create deployment flow-demo --image=nginx:stable --replicas=1
kubectl get events --sort-by=.metadata.creationTimestamp \
  -o custom-columns=SOURCE:.source.component,OBJECT:.involvedObject.kind,REASON:.reason,MESSAGE:.message \
  | grep flow-demo
실제 출력 (발생 순서대로 정리):
SOURCE                  OBJECT       REASON              MESSAGE
deployment-controller   Deployment   ScalingReplicaSet   Scaled up replica set flow-demo-7d6d49ff65 from 0 to 1
replicaset-controller   ReplicaSet   SuccessfulCreate    Created pod: flow-demo-7d6d49ff65-pjnqd
default-scheduler       Pod          Scheduled           Successfully assigned default/flow-demo-7d6d49ff65-pjnqd to desktop-worker2
kubelet                 Pod          Pulled              Container image "nginx:stable" already present on machine ...
kubelet                 Pod          Created             Container created
kubelet                 Pod          Started             Container started
파드가 실행되기까지 구성 요소의 연동 (위 출력과 대응):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. 사용자            kubectl create deployment ...
                       │ (API 요청)
2. kube-apiserver    요청을 검사하고 etcd에 디플로이먼트 저장
                       │
3. kube-controller-manager
   ├─ deployment-controller   "디플로이먼트에 RS가 없네" → RS 생성     (ScalingReplicaSet)
   └─ replicaset-controller   "RS에 파드가 0개네" → 파드 생성          (SuccessfulCreate)
                       │ 이 시점의 파드는 아직 노드가 정해지지 않은 상태
4. kube-scheduler    노드를 골라 파드에 기록                          (Scheduled → desktop-worker2)
                       │
5. kubelet           "내 노드에 배정된 파드가 있네" → containerd로 실행  (Pulled → Created → Started)
   (desktop-worker2)
                       │
6. kube-proxy        파드가 서비스 대상이면 iptables 규칙 갱신
용어 정리

  • kube-apiserver: 클러스터 조작의 유일한 창구. 모든 구성 요소가 이를 통해 통신
  • etcd: 클러스터의 모든 상태를 저장하는 분산 키-값 저장소
  • kube-scheduler: 새 파드를 어느 노드에서 실행할지 결정하는 스케줄러
  • kube-controller-manager: 디플로이먼트 컨트롤러 등 각종 컨트롤러를 실행·관리하는 구성 요소
  • PodSpec: 파드에 포함할 컨테이너, 이미지, 리소스 등을 정의한 명세 (매니페스트의 spec 부분)
  • CRI (Container Runtime Interface): kubelet이 컨테이너 런타임을 조작하기 위한 표준 인터페이스
  • kube-system: 쿠버네티스 자신의 구성 요소가 실행되는 네임스페이스
  • 스태틱 파드 (Static Pod): API 서버를 거치지 않고 kubelet이 노드의 설정 파일로 직접 띄우는 파드
  • 데몬셋 (DaemonSet): 모든(또는 지정한) 노드에 파드를 하나씩 배치하는 리소스
  • 리더 선출 (Leader Election): 같은 구성 요소가 여러 개 떠 있을 때 하나만 일하도록 정하는 방식

2026-10 갱신

  • "마스터"는 현재 공식 용어로 컨트롤 플레인(Control Plane) 이라고 함. 실습에서도 노드 역할이 control-plane으로 표시됨 (→ 5-1의 갱신 노트)
  • 워커의 컨테이너 런타임으로 도커는 더 이상 직접 사용하지 않음. kubelet이 도커를 쓰기 위한 중계 계층(dockershim)이 v1.24에서 제거되어, 현재는 containerd 또는 CRI-O 같은 CRI 런타임을 사용함. Docker Desktop의 쿠버네티스조차 실습에서 본 것처럼 containerd를 씀 (도커로 빌드한 이미지는 OCI 표준이므로 그대로 실행 가능)
  • 원서의 kube-proxy 설명("파드와 파드가 통신할 때")은 정확히는 서비스를 거치는 통신의 규칙을 관리하는 것임. 파드 간 직접 통신은 CNI 플러그인(5-1의 플라넬, 이 실습의 kindnet 등)이 담당
  • 클라우드 환경에서는 컨트롤 플레인에 클라우드 로드 밸런서·노드 연동을 담당하는 cloud-controller-manager도 함께 동작함


실습 정리하기

# 이 노트에서 만든 리소스 삭제
kubectl delete deployment my-deployment
kubectl delete service my-service
kubectl delete pod client resource-demo
kubectl delete hpa my-hpa                # 만들었다면

kubectl get all
# 실제 출력 (정리 후):
# NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
# service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   14d   ← 기본 서비스만 남음

핵심 요약

5-2장. 쿠버네티스의 가용성·스케일링·운영·아키텍처 요약:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[로드 밸런싱과 헬스 체크]
├─ 가용성: 원할 때 사용할 수 있는 정도, 가동률 = 정상 가동 시간 / 가동 시간
├─ 가용성 향상: 단일 장애 지점 제거 → 이중화 → LB·헬스 체크로 발견·교체 자동화
├─ 로드 밸런싱: 서비스 + kube-proxy가 iptables(또는 IPVS) NAT 규칙 관리
│   └─ 실습: iptables는 확률 1/3·1/2·나머지 규칙으로 무작위 균등 분배
├─ IPVS: 해시 테이블·커널 모드로 iptables보다 빠름 → 서비스 1만 개 초과 시 권장
└─ 헬스 체크: kubelet이 명령어·TCP·HTTP로 확인
    ├─ livenessProbe 실패 → 재시작 (실습: RESTARTS 1)
    └─ readinessProbe 실패 → 서비스 대상에서 제외 (실습: READY 0/1, ready=false)

[스케일링]
├─ 요청 집중 시 증가(다운 방지), 한산할 때 감소(비용 절약)
├─ 메트릭이 임곗값을 상회·하회하면 증감 (과거 데이터로 연속 여부 판단하기도 함)
├─ 수평(개수) vs 수직(성능) → 수평이 처리 능력과 선형 관계에 가까움
├─ HPA: 파드 수평 스케일링, 기본 메트릭은 CPU 사용률 → metrics-server 필요
├─ VPA: request(하한)·limit(상한) 자동 조정, 별도 설치 (In-place Resize로 무중단 가능)
└─ CA: 클라우드에서 노드 수 증감, 파드가 리소스 부족으로 Pending일 때 증가

[주요 명령어]
├─ 매니페스트(YAML/JSON)에 기대하는 상태를 선언 → 절차는 쿠버네티스가 계획
├─ 공통 구조: apiVersion · kind · metadata · spec
├─ 레이블(키-값) + 레이블 셀렉터로 여러 객체를 일괄 선택 → 객체 간 연결고리
├─ apply(created/configured/unchanged) / get / describe / exec / delete
├─ 레플리카셋은 셀렉터에 맞는 기존 파드도 입양, 지우면 자동 복구
└─ 파드 → 레플리카셋(template) → 디플로이먼트(kind만 다름) → 서비스(selector로 연결)

[자동 빌드]
├─ 자동 빌드: 컴파일·라이브러리 설치·테스트 자동화, 결과물 = 아티팩트
├─ 자동 배포: 서버 업로드·DB 변경 등 릴리스 절차 자동화
├─ 3단계: 이미지 생성 → 레지스트리 등록 → 배포 업데이트 (git push 등을 트리거로)
├─ 업데이트 전략: 재생성(전체 중단, 혼재 없음) / 롤링 업데이트(무중단, 혼재, 기본값)
│   └─ 실습: Recreate 실패 6건, Rolling 1건, Rolling + readiness + preStop 0건
├─ 롤백: 이전 레플리카셋을 0개로 남겨 두었다가 rollout undo로 되돌림
└─ 도구: Skaffold(구글) · 젠킨스 X · 스피내커(넷플릭스)

[멀티 클라우드]
├─ 여러 클라우드(퍼블릭·프라이빗)를 결합한 환경
├─ 필요성: 한 클라우드 장애 시 분리해 서비스 유지 → 가용성 향상
├─ 쿠버네티스: 클라우드마다 다른 절차를 같은 방식으로 조작 → 복잡성 감소
└─ 도구: Rancher (클러스터 구축, 자격 증명 중앙 관리, 접근 제어, 단일 화면)

[아키텍처]
├─ 사용자 → 마스터(명령) → 워커(조작), 마스터에는 NoSchedule 테인트
├─ 조작 방법: kubectl · 대시보드 · 쿠버네티스 API (앞의 둘도 내부적으로 API 사용)
├─ 마스터: kube-apiserver(창구) · etcd(저장소, 백엔드) · kube-scheduler(배치) · kube-controller-manager(컨트롤러)
│   └─ 실습: kube-system에 스태틱 파드로 실행, etcd에 /registry/... 키로 저장
└─ 워커: 컨테이너 런타임(containerd) · kubelet(PodSpec → CRI로 실행·감시) · kube-proxy(주소 변환 규칙)
    └─ 실습: 디플로이먼트 생성 시 deployment → replicaset → scheduler → kubelet 순서로 이벤트 발생

참고 자료

관련 자료:


네비게이션

이전 강 목록 다음 강
◀ 5-1. 컨테이너 오케스트레이션 도구, 쿠버네티스 - 파트 1 목록으로 6. 클라우드 컨테이너 서비스 (AWS) ▶